package edu.umassd.visual;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import edu.umassd.visual.util.Rectangle;

/**
 * The view contains three pieces of data that are required.
 * 
 * @author Dan Avila
 * 
 */
@Component
public class View
{
	private Rectangle view = new Rectangle(0.0, 0.0);
	private Rectangle dataSet = new Rectangle(0.0, 0.0);

	/**
	 * The scale factor of the previous zoom.
	 */
	private Double previousZoom = 100.0;

	@Autowired
	private SizedComponent canvas;

	@Value("${canvas.invert.y}")
	private boolean inverted;

	/**
	 * Sets the boundaries for the currently viewable dataset. This is useful
	 * for resetting and centering functions.
	 * 
	 * @param boundaries
	 *            - the boundaries of the dataset.
	 */
	public void setDataSetBounds(Rectangle rect)
	{
		Double zoom = previousZoom;
		this.zoom(100.0);
		this.dataSet.setRect(rect);
		this.view.setRect(rect);

		this.zoom(zoom);
	}

	/**
	 * Zooms in on a particular location. If the change between the current zoom
	 * and the next zoom is too small, this method does nothing.
	 * 
	 * @param zoomValue
	 *            - factors should be based on a 100% scale. To zoom in, specify
	 *            values greater than 100. For instance, to zoom in to half
	 *            size, use the value the value 200%. Double size would be 50%.
	 */
	public void zoom(Double zoomValue)
	{
		double previousZoomFactor = 100.0 / previousZoom;
		double scale = 100.0 / zoomValue;
		double hChange = dataSet.getWidth() * scale - dataSet.getWidth()
				* previousZoomFactor;
		double vChange = dataSet.getHeight() * scale - dataSet.getHeight()
				* previousZoomFactor;

		if (hChange == 0.0 || vChange == 0.0)
		{
			return;
		}

		double hAdjust = hChange / 2;
		double vAdjust = vChange / 2;

		this.view.grow(hAdjust, vAdjust);

		this.previousZoom = zoomValue;
	}

	/**
	 * Adjusts the screen in a direction.
	 * 
	 * @param x
	 *            - the horizontal value to adjust by.
	 * @param y
	 *            - the vertical value to adjust by.
	 */
	public void pan(double x, double y)
	{
		double offsetX = view.getWidth() / canvas.getWidth() * x;
		double offsetY = view.getHeight() / canvas.getHeight() * y;

		if (inverted)
		{
			this.view.setLocation(offsetX, -offsetY);
		}
		else
		{
			this.view.setLocation(offsetX, offsetY);
		}
	}

	/**
	 * Converts the incoming x coordinate from a location on the canvas to an
	 * actual point in the dataSet.
	 * 
	 * @param canvasX
	 *            - the x location of the mouse on the canvas.
	 * @return the equivalent x location in the dataset
	 */
	public Double canvasToDataX(double canvasX)
	{
		double scale = view.getWidth() / canvas.getWidth();

		return view.getX() + canvasX * scale;
	}

	/**
	 * Converts the incoming y coordinate from a location on the canvas to an
	 * actual point in the dataSet.
	 * 
	 * @param canvasY
	 *            - the y location of the mouse on the canvas.
	 * @return the equivalent y location in the dataset
	 */
	public Double canvasToDataY(double canvasY)
	{
		double scale = view.getHeight() / canvas.getHeight();

		if (inverted)
		{
			return view.getY() + (canvas.getHeight() - canvasY) * scale;
		}
		else
		{
			return view.getY() - canvasY * scale;
		}
	}

	/**
	 * Converts the incoming coordinate into the pixel location to draw to.
	 * 
	 * @param dataX
	 *            - the x coordinate of a particular piece of data.
	 * @return the x location to draw to on the canvas.
	 */
	public Double dataToCanvasX(double dataX)
	{
		double scale = canvas.getWidth() / view.getWidth();

		double base = view.getX() * scale;
		double value = dataX * scale;

		return value - base;
	}

	/**
	 * Converts the incoming coordinate into the pixel location to draw to.
	 * 
	 * @param dataY
	 *            - the y coordinate of a particular piece of data.
	 * @return the y location to draw to on the canvas.
	 */
	public Double dataToCanvasY(double dataY)
	{
		double scale = canvas.getHeight() / view.getHeight();
		double value = dataY * scale;

		if (inverted)
		{
			double base = view.getMaxY() * scale;

			return base - value;
		}
		else
		{
			double base = view.getY() * scale;

			return value - base;
		}
	}

	public double getWidth()
	{
		return view.getWidth();
	}

	public double getHeight()
	{
		return view.getHeight();
	}

	public void updateWidth(double width)
	{
		Rectangle rect = new Rectangle(dataSet.getX(), dataSet.getY(), width,
				dataSet.height);

		setDataSetBounds(rect);
	}

	public void updateHeight(double height)
	{
		Rectangle rect = new Rectangle(dataSet.getX(), dataSet.getY(),
				dataSet.width, height);

		setDataSetBounds(rect);
	}

	public void fitToScreen()
	{
		setDataSetBounds(dataSet);
	}
}
